package net.molchat.android.binroot;


import java.io.UnsupportedEncodingException;
import java.util.Vector;


/**
 * Main (and only) node class. Can be represented as a leaf and as node point at
 * the same time.
 * 
 * @author Валентин
 * 
 */
public class Root {

//======================================================================= Fields
public int nodeType;
public byte[] data;
public Vector children;


//================================================================ toBytes(long)
/**
 * Converts long (64 bit integer) value to byte array. Little-endian.
 * 
 * @param longVal
 *          - value to convert.
 * @return byte array with converted data.
 */
public static byte[] toBytes(long longVal) {

	// Calculate length
	int len = 1;
	long compUp = 0x7f;
	long compLow = -0x80;

	if (longVal >= 0) {
		while (longVal > compUp) {
			len++;
			compUp = (compUp << 8) | 0xff;
		}
	} else { // longval < 0
		while (longVal < compLow) {
			len++;
			compLow = compLow << 8;
		}
	}

	// Write data
	byte[] bytes = new byte[len];
	for (int q = 0; q < len; ++q) {
		bytes[q] = (byte) longVal;
		longVal >>>= 8;
	}

	return bytes;
}


//============================================================== toBytes(String)
/**
 * Converts string to byte array, UTF-8 encoded.
 * 
 * @param longVal
 *          - value to convert.
 * @return byte array with converted data.
 */
public static byte[] toBytes(String text) {

	byte[] bytes;
	try {
		bytes = text.getBytes("UTF-8");
	} catch (UnsupportedEncodingException e) {
		throw new ArrayStoreException(
				"Root.setData: UTF-8 encoding is NOT supported.");
	}
	return bytes;
}


//===================================================================== toString
private static void toString(Root root, int deep, boolean fLast, StringBuffer sb) {

	// Space
	for (int q = 0; q < (deep << 1); ++q) {
		sb.append(' ');
	}

	// Header
	sb.append('[');
	sb.append(root.nodeType);
	sb.append(']');
	if (fLast) {
		sb.append('+');
	} else {
		sb.append('-');
	}
	if (root.children != null) {
		sb.append('+');
	} else {
		sb.append('-');
	}

	// Short space
	sb.append(' ');


	// Content
	if (root.data != null) {
		// Length
		sb.append('(').append(root.data.length).append(')');

		// Data bytes (if any)
		if (root.data.length > 0) {
			// Short space
			sb.append(' ');

			for (int q = 0; q < root.data.length; ++q) {
				sb.append(root.data[q]).append(", ");
			}
		}
	}

	// New line
	sb.append('\n');

	// Children
	if (root.children != null) {
		int childNum = 0;
		int childLast = root.children.size() - 1;
		while (childNum <= childLast) {
			toString((Root) (root.children.elementAt(childNum)), ++deep,
					(childNum == childLast), sb);
			deep--;
			childNum++;
		}
	}
}


//================================================================== Constructor
public Root() {

}


public Root(int nodeType) {

	this.nodeType = nodeType;
}


//===================================================================== addChild
public void addChild(Root child) {

	if (this.children == null) {
		this.children = new Vector();
	}
	this.children.addElement(child);
}


//====================================================================== addLong
/**
 * Creates and appends long integer child.
 * 
 * @param id
 * @param longVal
 */
public Root addLong(int id, long longVal) {

	Root root = new Root(id);
	root.data = Root.toBytes(longVal);

	this.addChild(root);

	return root;
}


//==================================================================== addString
/**
 * Creates and appends string child.
 * 
 * @param id
 * @param longVal
 */
public Root addString(int id, String text) {

	Root root = new Root(id);
	root.data = Root.toBytes(text);

	this.addChild(root);

	return root;
}


//======================================================================= asLong
/**
 * Returns this root data as long value (converts byte[] to long in the
 * little-endian order).
 * 
 * @return
 */
public long asLong() {

	if (this.data != null) {
		int p = data.length - 1;
		long longVal = this.data[p] < 0 ? -1 : 0;
		longVal = this.data[p];
		p--;
		for (; p >= 0; --p) {
			longVal <<= 8;
			longVal |= this.data[p] & 0xff;
		}
		return longVal;
	} else {
		throw new NullPointerException("Root (nodeType = " + nodeType
				+ ") data is null");
	}

}


//===================================================================== asString
public String asString() {

	if (this.data != null) {
		try {
			return new String(this.data, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new ArrayStoreException(
					"Root.setData: UTF-8 encoding is NOT supported.");
		}
	} else {
		return null;
	}
}


//================================================================== childAsLong
public long childAsLong(int type) {

	return getChild(type).asLong();
}


public long childAsLong(int type, long defaultValue) {

	Root child = getChild(type);
	if (child != null) {
		return child.asLong();
	} else {
		return defaultValue;
	}
}


//================================================================ childAsString
public String childAsString(int type) {

	Root child = getChild(type);
	if (child != null) {
		return child.asString();
	} else {
		return null;
	}
}


//===================================================================== getChild
public Root getChild(int type) {

	if (this.children != null) {
		for (int q = this.children.size() - 1; q >= 0; --q) {
			Root child = (Root) children.elementAt(q);
			if (child.nodeType == type) {
				return child;
			}
		}
	}
	return null;
}


//================================================================== getChildren
public Vector getChildren(int type) {

	Vector children = null;

	if (this.children != null) {
		children = new Vector();
		for (int q = this.children.size() - 1; q >= 0; --q) {
			Root child = (Root) children.elementAt(q);
			if (child.nodeType == type) {
				children.addElement(child);
			}
		}
		if (children.size() == 0) {
			children = null;
		}
	}

	return children;
}


//====================================================================== setData
public void setData(long longVal) {

	this.data = Root.toBytes(longVal);
}


//====================================================================== setData
public void setData(String text) {

	if (text != null) {
		this.data = toBytes(text);
	} else {
		this.data = null;
	}
}


public String toString() {

	StringBuffer sb = new StringBuffer();
	Root.toString(this, 0, true, sb);

	return sb.toString();
}


}
